#include <WiFi.h>

// Adafruit NeoPixel by Ada. Version 1.8.2
#include <Adafruit_NeoPixel.h> //RGB灯控制
#undef DEBUG_PRINTLN           //由于红外模块和DHT重复定义了这两个函数
#undef DEBUG_PRINT             //需要取消定义修复编译报错

// DHT sensor library by Ada. Version 1.4.2
#include "DHT.h" //温湿度传感器

// IRRemoteESP8266 By David... 2.7.20
#include <IRremoteESP8266.h>
#include <IRrecv.h>
#include <IRutils.h> //这三一套的

// Bounce2 by Thomas O Fredericks Version 2.60.0
#include <Bounce2.h>

// LiquidCrystal_PCF8574 by Ma.Her. Version 1.3.0
#include <LiquidCrystal_PCF8574.h>

// ESP32Servo Library by Kevin Harrington Version 0.9.0
#include <ESP32Servo.h>

// WIFI网络相关
#include <DNSServer.h>
#include <esp_wifi.h>
#include <WebServer.h>
// ArduinoJson by B.B. Version 6.18.5
#include <ArduinoJson.h>
#include <WiFiUdp.h>
#include <memory>
#undef min
#undef max
#include <algorithm>

// NTPClient 3.2.0 by Fa. We.
#include <NTPClient.h> //时间同步

#include "portal_config.h"

#include "TM1637.h"

byte tempUnit[] = {
    B10000,
    B00110,
    B01001,
    B01000,
    B01000,
    B01001,
    B00110,
    B00000};

//******引脚定义部分******
//定义红外接收器IO
int PIN_IR_RECEIVE = 13;
// 控制 WS2812 灯条DIN的引脚编号IO
int PIN_LIGHT = 12;
//温度传感器的输入DATA引脚IO
int PIN_DHT = 14;
//风扇驱动控制IO
int PIN_FAN = 23;
//蜂鸣器IO
int PIN_BUZZER = 26;
//窗按钮IO
int PIN_WINDOWBUTTON = 25;
//门舵机IO
int PIN_DOORGEAR = 32;
//遮阳板舵机IO
int PIN_COVERGEAR = 33;
//人传感器IOPIN
int PIN_HUMANDETECT = 36;
//定义光敏传感器接口
int PIN_LIGHTDETECT = 39;
//定义火焰传感器接口
int PIN_FIREDETECT = 34;
//火焰报警接口
int PIN_FIREALARM = 18;
//人报警接口
int PIN_HUMANALARM = 19;
//钟CLK
int PIN_TIMECLK = 4;
//钟DIO
int PIN_TIMEDIO = 5;

//显示屏IIC地址(当前SDA接A4,SCL接A5)IO
const int IICADDR = 0x27; // 39;

//******状态定义部分******
//门状态 0关 1开
int doorState = 0;
//灯当前颜色 0为复杂模式，1为日光，2为暖光，3为白光
int lightColor = 0;
//灯当前亮度 0关 1-5对应5-255
int lightLevel = 0;
//风扇开关 0关 1自动 2常开
int fanOpt = 1;
//风扇当前状态 0关 1开
int fanState = 0;
//风扇自动开启温度
float fanTemp = 30;
//防盗模式 0关 1开
int secOpt = 1;
//警报状态 0未报警 1报警中
int secState = 0;
//火警状态 0未报警 1报警中
int fireState = 0;
//窗状态 0关 1开
int winState = 0;
//遮阳板状态 0关 1自动 2开，关时为遮阳状态
int coverOpt = 1;
//遮阳板当前状态 0关 1开，关时为遮阳状态
int coverState = 1;
//温度
float humidityVal = 66.66;
//湿度
float tempVal = 23.33;
//光照强度分级
int illuVal = 0;
//自动遮阳板阈值
int coverIllu = 35;

//门密码
int password = 123456;

//******其他全局变量******

String _ssid = "";
String _pass = "";
bool startConnect = false;

//屏幕显示内容
String dispLine1 = "";
String dispLine2 = "";
unsigned long dispUpdateTime = -1000;
int refreshRate = 3000;

//是否已经获取过一次数据
bool alreadySetup = false;

// WS2812灯珠数量
int LIGHTNUM = 8;

//是否为离线模式
bool offlineMode = false;

//灯自定义颜色RGB数组
int lightCustom[3] = {0, 255, 0};

//上传周期控制
//上传等待循环次数
int UPLOADCYCLE = 10;
//当前循环数
int upCycle = 0;

//钟上次更新时间
unsigned long timeUpdateTime = -1000;
//钟是否有点
bool clockPoint = false;

//温度刷新控制
//刷新等待循环次数
int DISPCYCLE = 100;
//当前循环数
int dCycle = 0;

//当前周期是否发送数据,假为接收数据
bool sendOrDownload = true;

//密码数字与按键command对应表
unsigned long pwCommand[10] = {0xff9867,
                               0xffa25d, 0xff629d, 0xffe21d,
                               0xff22dd, 0xff02fd, 0xffc23d,
                               0xffe01f, 0xffa857, 0xff906f};
//密码当前匹配位数
long pair = 100000l;

int winButtonVal = 1;      //变量val存储按钮的状态，这里请根据传感器默认返回值输入
int winButton_old_val = 1; //存储val变量的上一个时间状态，初始值请和val的初始值保持一致
int winButstate = 0;       //打印到串口的数据，按钮每被按下一次，state自增1
decode_results IrResults;

//******全局`对象`******
WiFiClient client;
std::unique_ptr<DNSServer> dnsServer;
std::unique_ptr<WebServer> server;
WiFiUDP ntpUDP;
Adafruit_NeoPixel pixels(LIGHTNUM, PIN_LIGHT, NEO_GRB + NEO_KHZ800);
DHT dht(PIN_DHT, DHT22);
LiquidCrystal_PCF8574 dispLcd(IICADDR);
Bounce debouncer = Bounce(); //实例化一个抖动对象
Servo doorServo;
Servo coverServo;
IRrecv IrReceiver(PIN_IR_RECEIVE);
NTPClient timeClient(ntpUDP, "ntp.aliyun.com", 0, 300000);
TM1637 tm1637(PIN_TIMECLK, PIN_TIMEDIO);

void doorClose()
{
    // Serial.println("door close");
    doorServo.write(180);
    delay(500);
    doorState = 0;
}
void doorOpen()
{

    // Serial.println("door open");
    doorServo.write(0);
    delay(500);
    doorState = 1;
}
void coverClose()
{
    // Serial.println("cover close");
    coverServo.write(0);
    delay(500);
    coverState = 0;
}
void coverOpen()
{
    // Serial.println("cover open");
    coverServo.write(180);
    delay(500);
    coverState = 1;
}
void pairPassword(unsigned long command)
{
    // Serial.println(pwCommand[password / pair % 10]);
    // Serial.println(command);
    // Serial.println(pair);
    if (command == pwCommand[password / pair % 10])
    {
        Serial.println("pair");
        if (pair == 1)
        {
            pair = 100000l;
            doorOpen();
        }
        else
        {
            pair /= 10;
        }
    }
    else
    {
        Serial.println("not pair");
        pair = 100000l;

        if (doorState == 1)
        {
            doorClose();
        }
    }
}

//设置风扇开关
void fanOptSet(int newState)
{
    //重构优秀范例了属于是，后面的操控都不那么麻烦分开写了
    switch (newState)
    {
    case 0:
        digitalWrite(PIN_FAN, LOW);
        fanState = 0;
        fanOpt = 0;
        dispLine1 = "FAN OFF";
        dispLine2 = "";
        dispUpdateTime = millis() - refreshRate;
        break;
    case 1:
        fanOpt = 1;
        dispLine1 = "FAN AUTO";
        dispLine2 = "";
        dispUpdateTime = millis() - refreshRate;
        break;
    case 2:
        digitalWrite(PIN_FAN, HIGH);
        fanState = 1;
        fanOpt = 2;
        dispLine1 = "FAN ON";
        dispLine2 = "";
        dispUpdateTime = millis() - refreshRate;
        break;
    default:
        break;
    }
}
void fanControl()
{
    if (fanOpt < 2)
    {
        fanOptSet(++fanOpt);
    }
    else
    {
        fanOptSet(0);
    }
}

void coverOptSet(int newState)
{
    //重构优秀范例了属于是，后面的操控都不那么麻烦分开写了
    switch (newState)
    {
    case 0:
        dispLine1 = "COVER CLOSE";
        dispLine2 = "";
        dispUpdateTime = millis() - refreshRate;
        coverClose();
        coverOpt = 0;
        break;
    case 1:
        dispLine1 = "COVER AUTO";
        dispLine2 = "";
        dispUpdateTime = millis() - refreshRate;
        coverOpt = 1;
        break;
    case 2:
        dispLine1 = "COVER OPEN";
        dispLine2 = "";
        dispUpdateTime = millis() - refreshRate;
        coverOpen();
        coverOpt = 2;
        break;
    default:
        break;
    }
}
void coverControl()
{
    if (coverOpt < 2)
    {
        coverOptSet(++coverOpt);
    }
    else
    {
        coverOptSet(0);
    }
}
//光变颜色
void lightChangeColor()
{
    switch (lightColor)
    {
    case 0:
        for (int i = 0; i < LIGHTNUM; i++)
        {
            pixels.setPixelColor(i, pixels.Color(255, 255, 255));
        }
        lightColor++;
        break;
    case 1:
        for (int i = 0; i < LIGHTNUM; i++)
        {
            pixels.setPixelColor(i, pixels.Color(254, 243, 150));
        }
        lightColor++;
        break;
    case 2:
        for (int i = 0; i < LIGHTNUM; i++)
        {
            pixels.setPixelColor(i, pixels.Color(252, 213, 71));
        }
        lightColor++;
        break;
    case 3:
        for (int i = 0; i < LIGHTNUM; i++)
        {
            pixels.setPixelColor(i, pixels.Color(lightCustom[0], lightCustom[1], lightCustom[2]));
        }
        lightColor = 0;
        break;
    default:
        break;
    }
    pixels.show();
}

//灯等级设定0-5
void lightSetLevel(int newLevel)
{
    switch (newLevel)
    {
    case 0:
        pixels.setBrightness(0);
        break;
    case 1:
        pixels.setBrightness(10);
        break;
    case 2:
        pixels.setBrightness(30);
        break;
    case 3:
        pixels.setBrightness(80);
        break;
    case 4:
        pixels.setBrightness(120);
        break;
    case 5:
        pixels.setBrightness(255);
        break;
    default:
        break;
    }
    Serial.println("BP2.2");

    if (lightLevel == 0)
    {
        //如原亮度为0，设置亮度后需要重新设置颜色才能点亮
        // for (int i = 0; i < LIGHTNUM; i++)
        // {
        //     pixels.setPixelColor(i, pixels.Color(255, 255, 255));
        // }
        if (lightColor > 0)
        {
            lightColor--;
        }
        else
        {
            lightColor = 3;
        }
        Serial.println("BP2.3");

        lightChangeColor();
    }
    lightLevel = newLevel;
    Serial.println("BP2.4");

    pixels.show();

    Serial.println("BP2.5");

    // dispLcd.clear();
    // dispLcd.setCursor(0, 0); //设置光标
    // dispLcd.print("LIGHT BRIGHTNESS");
    // dispLcd.setCursor(0, 1);
    // dispLcd.print("LEVEL: ");
    // dispLcd.print(lightLevel);
    dispLine1 = "LIGHT BRIGHTNESS";
    dispLine2 = "LEVEL: " + String(lightLevel);
    dispUpdateTime = millis() - refreshRate;
    Serial.println("BP2.6");
}
void lightIncrease()
{
    switch (lightLevel)
    {
    case 0:
        lightSetLevel(1);
        break;
    case 1:
        lightSetLevel(2);
        break;
    case 2:
        lightSetLevel(3);
        break;
    case 3:
        lightSetLevel(4);
        break;
    case 4:
        lightSetLevel(5);
        break;
    default:
        break;
    }
}

void lightDecrease()
{
    switch (lightLevel)
    {
    case 1:
        lightSetLevel(0);
        break;
    case 2:
        lightSetLevel(1);
        break;
    case 3:
        lightSetLevel(2);
        break;
    case 4:
        lightSetLevel(3);
        break;
    case 5:
        lightSetLevel(4);
        break;
    default:
        break;
    }
}

void winStateUpdater()
{
    debouncer.update();                                                                                                   //更新
    winButtonVal = debouncer.read();                                                                                      ////读取输入数值并且存储
    if (((winButtonVal == LOW) && (winButton_old_val == HIGH)) || ((winButtonVal == HIGH) && (winButton_old_val == LOW))) //检查按钮的变化情况，请注意val的比较值是当按钮传感器按下去返回的值，old_val和val相反即可。
    {
        winButstate++; // state变量增加1
        if (winButstate % 2 == 1)
        {
            winState = 0;
        }
        else if (winButstate % 2 == 0)
        {
            winState = 1;
        }
        winButton_old_val = winButtonVal; // val现在是旧的了，暂存一下
    }
}

//设置防盗模式，2为切换开关
void setSecMode(int mode)
{
    switch (mode)
    {
    case 0:
        secOpt = 0;
        dispLine1 = "SECURITY MODE";
        dispLine2 = "     OFF";
        dispUpdateTime = millis() - refreshRate;
        break;
    case 1:
        secOpt = 1;
        dispLine1 = "SECURITY MODE";
        dispLine2 = "      ON";
        dispUpdateTime = millis() - refreshRate;
        break;
    case 2:
        if (secOpt)
        {
            setSecMode(0);
        }
        else
        {
            setSecMode(1);
        }
        break;
    default:
        break;
    }
}

void sendData()
{
    StaticJsonDocument<1000> doc;
    JsonArray uploadsArr = doc.createNestedArray("uploads"); //{"uploads":[]}

    if (ID_FAN != NULL)
    {
        StaticJsonDocument<200> pieceData;
        pieceData["id"] = ID_FAN;
        pieceData["route"] = "fanUpload";
        JsonObject nData = pieceData.createNestedObject("data"); //添加一个对象节点
        nData["stage"] = fanOpt;
        nData["humi"] = humidityVal;
        nData["temp"] = tempVal;
        uploadsArr.add(pieceData);
        pieceData.clear();
    }
    if (ID_DOOR != NULL)
    {
        StaticJsonDocument<200> pieceData;
        pieceData["id"] = ID_DOOR;
        pieceData["route"] = "doorUpload";
        JsonObject nData = pieceData.createNestedObject("data"); //添加一个对象节点
        nData["state"] = doorState;
        uploadsArr.add(pieceData);
        pieceData.clear();
    }
    if (ID_LIGHT != NULL)
    {
        StaticJsonDocument<200> pieceData;
        pieceData["id"] = ID_DOOR;
        pieceData["route"] = "lightUpload";
        JsonObject nData = pieceData.createNestedObject("data"); //添加一个对象节点
        nData["level"] = lightLevel;
        nData["color"] = lightColor;
        uploadsArr.add(pieceData);
        pieceData.clear();
    }
    if (ID_COVER != NULL)
    {
        StaticJsonDocument<200> pieceData;
        pieceData["id"] = ID_COVER;
        pieceData["route"] = "coverUpload";
        JsonObject nData = pieceData.createNestedObject("data"); //添加一个对象节点
        nData["stage"] = coverOpt;
        nData["illu"] = illuVal;
        uploadsArr.add(pieceData);
        pieceData.clear();
    }
    if (ID_SECURE != NULL)
    {
        StaticJsonDocument<200> pieceData;
        pieceData["id"] = ID_SECURE;
        pieceData["route"] = "secUpload";
        JsonObject nData = pieceData.createNestedObject("data"); //添加一个对象节点
        nData["state"] = secState;
        nData["secmode"] = secOpt;
        uploadsArr.add(pieceData);
        pieceData.clear();
    }
    if (ID_FIREALARM != NULL)
    {
        StaticJsonDocument<200> pieceData;
        pieceData["id"] = ID_FIREALARM;
        pieceData["route"] = "fireUpload";
        JsonObject nData = pieceData.createNestedObject("data"); //添加一个对象节点
        nData["state"] = fireState;
        uploadsArr.add(pieceData);
        pieceData.clear();
    }
    if (ID_WINDOW != NULL)
    {
        StaticJsonDocument<200> pieceData;
        pieceData["id"] = ID_WINDOW;
        pieceData["route"] = "windowUpload";
        JsonObject nData = pieceData.createNestedObject("data"); //添加一个对象节点
        nData["state"] = winState;
        uploadsArr.add(pieceData);
        pieceData.clear();
    }
    char dataString[measureJson(doc) + 1];
    serializeJson(doc, dataString, measureJson(doc) + 1);
    client.print(dataString);
}

void getOpt()
{
    StaticJsonDocument<800> doc;
    JsonArray uploadsArr = doc.createNestedArray("uploads"); //{"uploads":[]}
    String route = alreadySetup ? "ctrlDownload" : "setupDownload";

    if (ID_FAN != NULL)
    {
        StaticJsonDocument<200> pieceData;
        pieceData["id"] = ID_FAN;
        pieceData["route"] = route;
        uploadsArr.add(pieceData);
        pieceData.clear();
    }
    if (ID_DOOR != NULL)
    {
        StaticJsonDocument<200> pieceData;
        pieceData["id"] = ID_DOOR;
        pieceData["route"] = route;
        uploadsArr.add(pieceData);
        pieceData.clear();
    }
    if (ID_LIGHT != NULL)
    {
        StaticJsonDocument<200> pieceData;
        pieceData["id"] = ID_LIGHT;
        pieceData["route"] = route;
        uploadsArr.add(pieceData);
        pieceData.clear();
    }
    if (ID_SECURE != NULL)
    {
        StaticJsonDocument<200> pieceData;
        pieceData["id"] = ID_SECURE;
        pieceData["route"] = route;
        uploadsArr.add(pieceData);
        pieceData.clear();
    }
    if (ID_COVER != NULL)
    {
        StaticJsonDocument<200> pieceData;
        pieceData["id"] = ID_COVER;
        pieceData["route"] = route;
        uploadsArr.add(pieceData);
        pieceData.clear();
    }

    char dataString[measureJson(doc) + 1];
    serializeJson(doc, dataString, measureJson(doc) + 1);
    client.print(dataString);
}

void processResp(String resp)
{
    StaticJsonDocument<500> doc;
    char buf[500];
    resp.toCharArray(buf, 500);
    DeserializationError error = deserializeJson(doc, buf);
    if (error) //检查反序列化是否成功
    {
        // handleErr()
        ESP.restart();
    }
    if (doc["success"].as<int>() != 1)
    {
        // handleErr()
        const char *errm = doc["errmsg"];
        Serial.println(errm);
        ESP.restart();
    }
    else if (!doc["opt"].isNull())
    {
        for (int i = 0; !doc["opt"][i].isNull(); i++)
        { //到这里,doc["opt"][i]就是单个配置，
            // char dataString[measureJson(doc["opt"][i]) + 1];
            // serializeJson(doc["opt"][i], dataString, measureJson(doc["opt"][i]) + 1);
            // Serial.println(dataString);
            String optName = doc["opt"][i]["optName"];
            if (optName.equals("changePW"))
            {
                //改密码
                // setDoorPW(doc["opt"][i]["newPassword"].as<int>());
                Serial.println("change pw to:");
                Serial.println(doc["opt"][i]["newPassword"].as<int>());
                password = doc["opt"][i]["newPassword"].as<int>();
                pair = 100000l;
            }
            else if (optName.equals("changeDoorState"))
            {
                if (doc["opt"][i]["newState"].as<int>() == 1)
                {
                    doorOpen();
                }
                else if (doc["opt"][i]["newState"].as<int>() == 0)
                {
                    doorClose();
                }
            }
            else if (optName.equals("changeLight"))
            {
                Serial.println("BP1");
                if (!doc["opt"][i]["level"].isNull())
                {
                    Serial.println("BP2");

                    int newLevel = doc["opt"][i]["level"].as<int>();
                    Serial.println("BP2.1");

                    lightSetLevel(newLevel);
                }
                Serial.println("BP3");

                if (!doc["opt"][i]["color"].isNull())
                {
                    Serial.println("BP4");

                    int newColor = doc["opt"][i]["color"].as<int>();
                    if (newColor == 0)
                    {
                        lightColor = 3;
                        lightCustom[0] = doc["opt"][i]["R"].as<int>();
                        lightCustom[1] = doc["opt"][i]["G"].as<int>();
                        lightCustom[2] = doc["opt"][i]["B"].as<int>();
                    }
                    else
                    {
                        lightColor = newColor - 1;
                    }
                    Serial.println("BP5");

                    lightChangeColor();
                    Serial.println("BP6");
                }
            }
            else if (optName.equals("changeSecMode"))
            {
                setSecMode(doc["opt"][i]["secmode"].as<int>());
            }
            else if (optName.equals("changeFanState"))
            {
                if (!doc["opt"][i]["newState"].isNull())
                    fanOptSet(doc["opt"][i]["newState"].as<int>());
                if (!doc["opt"][i]["newOpenTemp"].isNull())
                    fanTemp = doc["opt"][i]["newOpenTemp"].as<float>();
            }
            else if (optName.equals("changeCoverState"))
            {
                if (!doc["opt"][i]["newState"].isNull())
                    coverOptSet(doc["opt"][i]["newState"].as<int>());
                if (!doc["opt"][i]["newOpenIllu"].isNull())
                    coverIllu = doc["opt"][i]["newOpenIllu"].as<int>();
            }
        }
    }
    else
    {
        Serial.println("Uploaded.");
    }
}
void getTempHumi()
{
    humidityVal = dht.readHumidity();
    tempVal = dht.readTemperature();
    if (dCycle < DISPCYCLE)
    {
        dCycle++;
    }
    else
    {
        dCycle = 0;
        dispLine1 = "Temp.: " + String(tempVal) + " ";
        dispLine2 = "Humi.: " + String(humidityVal) + " %";
        // dispLcd.clear();
        // dispLcd.setCursor(0, 0); //设置光标
        // dispLcd.print("Temp.: ");
        // dispLcd.print(tempVal);
        // dispLcd.print(" ");
        // dispLcd.write(byte(0));
        // dispLcd.setCursor(0, 1);
        // dispLcd.print("Humi.: ");
        // dispLcd.print(humidityVal);
        // dispLcd.print(" % ");
    }

    if (fanOpt == 1)
    {
        if (tempVal > fanTemp && fanState == 0)
        {
            digitalWrite(PIN_FAN, HIGH);
            fanState = 1;
            dispLine1 = "FAN AUTO ON";
            dispLine2 = "";
            dispUpdateTime = millis() - refreshRate;
        }
        else if (tempVal < fanTemp && fanState == 1)
        {
            dispLine1 = "FAN AUTO OFF";
            dispLine2 = "";
            dispUpdateTime = millis() - refreshRate;
            digitalWrite(PIN_FAN, LOW);
            fanState = 0;
        }
    }
}
void getIllu()
{
    illuVal = analogRead(PIN_LIGHTDETECT) / 100;
    if (coverOpt == 1)
    {
        if (illuVal < coverIllu && coverState == 0)
        {
            dispLine1 = "COVER AUTO ON";
            dispLine2 = "";
            dispUpdateTime = millis() - refreshRate;
            coverOpen();
        }
        else if (illuVal > coverIllu && coverState == 1)
        {
            dispLine1 = "COVER AUTO OFF";
            dispLine2 = "";
            dispUpdateTime = millis() - refreshRate;
            coverClose();
        }
    }
}
// //开启蜂鸣器警报，0为火警1为人警报
// void startAlarm(int alarmType)
// {
//     if (alarmType == 1)
//     {
//         digitalWrite(PIN_HUMANALARM,HIGH);
//     }
//     else
//     {
//         digitalWrite(PIN_HUMANALARM,HIGH);

//     }
// }
// //关闭蜂鸣器警报
// void stopAlarm(int alarmType)
// {

// }
void alarmUpdater()
{
    if (digitalRead(PIN_FIREDETECT) == LOW && fireState == 0)
    {
        fireState = 1;
        digitalWrite(PIN_FIREALARM, HIGH);
        // startAlarm(0);
    }
    else if (fireState == 1 && digitalRead(PIN_FIREDETECT) == HIGH)
    {
        fireState = 0;
        digitalWrite(PIN_FIREALARM, LOW);
        // stopAlarm(0);
    }

    if (secOpt == 1 && (digitalRead(PIN_HUMANDETECT) == HIGH || winState == 1) && secState == 0)
    {
        secState = 1;
        digitalWrite(PIN_HUMANALARM, HIGH);
        // startAlarm(1);
    }
    else if (secOpt == 0 || secState == 1 && (digitalRead(PIN_HUMANDETECT) == LOW && winState == 0))
    {
        secState = 0;
        digitalWrite(PIN_HUMANALARM, LOW);
        // stopAlarm(1);
    }
}

String WiFi_SSID()
{
    if (WiFi.getMode() == WIFI_MODE_NULL)
    {
        return String();
    }

    wifi_ap_record_t info;

    if (!esp_wifi_sta_get_ap_info(&info))
    {
        return String(reinterpret_cast<char *>(info.ssid));
    }
    else
    {
        wifi_config_t conf;
        esp_wifi_get_config(WIFI_IF_STA, &conf);
        return String(reinterpret_cast<char *>(conf.sta.ssid));
    }

    return String();
}
int scanWifiNetworks(int **indicesptr)
{
    int n = WiFi.scanNetworks(false, false, true, 100);

    if (n <= 0)
    {
        return (0);
    }
    else
    {
        // Allocate space off the heap for indices array.
        // This space should be freed when no longer required.
        int *indices = (int *)malloc(n * sizeof(int));

        if (indices == NULL)
        {
            *indicesptr = NULL;
            return (0);
        }

        *indicesptr = indices;

        // sort networks
        for (int i = 0; i < n; i++)
        {
            indices[i] = i;
        }

        // RSSI SORT
        // old sort
        for (int i = 0; i < n; i++)
        {
            for (int j = i + 1; j < n; j++)
            {
                if (WiFi.RSSI(indices[j]) > WiFi.RSSI(indices[i]))
                {
                    std::swap(indices[i], indices[j]);
                }
            }
        }

        String cssid;
        for (int i = 0; i < n; i++)
        {
            if (indices[i] == -1)
                continue;

            cssid = WiFi.SSID(indices[i]);
            for (int j = i + 1; j < n; j++)
            {
                if (cssid == WiFi.SSID(indices[j]))
                {
                    indices[j] = -1; // set dup aps to index -1
                }
            }
        }

        return (n);
    }
}
/** Is this an IP? */
bool isIp(String str)
{
    for (unsigned int i = 0; i < str.length(); i++)
    {
        int c = str.charAt(i);

        if (c != '.' && (c < '0' || c > '9'))
        {
            return false;
        }
    }
    return true;
}
String toStringIp(IPAddress ip)
{
    String res = "";
    for (int i = 0; i < 3; i++)
    {
        res += String((ip >> (8 * i)) & 0xFF) + ".";
    }

    res += String(((ip >> 8 * 3)) & 0xFF);

    return res;
}
bool captivePortal()
{

    if (!isIp(server->hostHeader()))
    {
        Serial.println("Redirected");

        server->sendHeader(F("Location"), (String)F("http://") + toStringIp(server->client().localIP()), true);
        server->send(302, FPSTR(HTTP_HEAD_CT2), ""); // Empty content inhibits Content-length header so we have to close the socket ourselves.
        server->client().stop();                     // Stop is needed because we sent no content length

        return true;
    }
    return false;
}
void handleRoot()
{
    if (captivePortal())
    {
        // If caprive portal redirect instead of displaying the error page.
        return;
    }
    server->sendHeader(FPSTR(HTTP_CACHE_CONTROL), FPSTR(HTTP_NO_STORE));
    server->sendHeader(FPSTR(HTTP_PRAGMA), FPSTR(HTTP_NO_CACHE));
    server->sendHeader(FPSTR(HTTP_EXPIRES), "-1");
    // String page = FPSTR(HTTP_INDEX);
    // page.replace("{devices}", DEVICE_IDS);
    // page = page.substring(0, page.indexOf("</html>") + 7);
    server->send(200, "text/html", FPSTR(HTTP_INDEX));
}
void handleWifiSave()
{
    _ssid = server->arg("ssid").c_str();
    _pass = server->arg("password").c_str();
    startConnect = true;
}
String wifi_type(int typecode)
{

#ifdef ESP8266
    if (typecode == ENC_TYPE_NONE)
        return "Open";
    if (typecode == ENC_TYPE_WEP)
        return "WEP ";
    if (typecode == ENC_TYPE_TKIP)
        return "WPA ";
    if (typecode == ENC_TYPE_CCMP)
        return "WPA2";
    if (typecode == ENC_TYPE_AUTO)
        return "WPA*";
    return "OTHER";
#else // ESP32
    if (typecode == WIFI_AUTH_OPEN)
        return "Open";
    if (typecode == WIFI_AUTH_WEP)
        return "WEP ";
    if (typecode == WIFI_AUTH_WPA_PSK)
        return "WPA ";
    if (typecode == WIFI_AUTH_WPA_WPA2_PSK || typecode == WIFI_AUTH_WPA2_PSK)
        return "WPA2";
    if (typecode == WIFI_AUTH_WPA2_ENTERPRISE)
        return "WPA2-EAP";
    return "OTHER";
#endif
}
void handleScan()
{
    server->sendHeader(FPSTR(HTTP_CACHE_CONTROL), FPSTR(HTTP_NO_STORE));
    server->sendHeader(FPSTR(HTTP_PRAGMA), FPSTR(HTTP_NO_CACHE));
    server->sendHeader(FPSTR(HTTP_EXPIRES), "-1");
    int n;
    int *indices;
    n = scanWifiNetworks(&indices);
    const char JSON_ITEM[] = "{\"ssid\":\"{v}\", \"encryptionType\":\"{i}\", \"rssi\":{r}}";
    String page = F("{\"req\":[");
    for (int i = 0; i < n; i++)
    {
        if (indices[i] == -1)
            continue; // skip duplicates and those that are below the required quality

        if (i != 0)
            page += F(", ");

        String item = FPSTR(JSON_ITEM);
        item.replace("{v}", WiFi.SSID(indices[i]));
        item.replace("{r}", (String)WiFi.RSSI(indices[i]));
        item.replace("{i}", wifi_type(WiFi.encryptionType(indices[i])));
        page += item;
        delay(0);
    }
    if (indices)
    {
        free(indices); // indices array no longer required so free memory
    }
    page += F("]}");
    server->send(200, "application/json", page);
}
void handleNotFound()
{
    if (captivePortal())
    {
        // If caprive portal redirect instead of displaying the error page.
        return;
    }

    String message = "File Not Found\n\n";

    message += "URI: ";
    message += server->uri();
    message += "\nMethod: ";
    message += (server->method() == HTTP_GET) ? "GET" : "POST";
    message += "\nArguments: ";
    message += server->args();
    message += "\n";

    for (uint8_t i = 0; i < server->args(); i++)
    {
        message += " " + server->argName(i) + ": " + server->arg(i) + "\n";
    }

    server->sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
    server->sendHeader("Pragma", "no-cache");
    server->sendHeader("Expires", "-1");

    server->send(404, "text/plain", message);
}
void handleDevices()
{
    server->sendHeader(FPSTR(HTTP_CACHE_CONTROL), FPSTR(HTTP_NO_STORE));
    server->sendHeader(FPSTR(HTTP_PRAGMA), FPSTR(HTTP_NO_CACHE));
    server->sendHeader(FPSTR(HTTP_EXPIRES), "-1");
    // compute the required size
    StaticJsonDocument<800> doc;
    JsonArray array = doc.to<JsonArray>();
    if (ID_FAN != NULL)
    {
        StaticJsonDocument<200> pieceData;
        pieceData["dName"] = "Smart Fan";
        pieceData["dId"] = ID_FAN;
        doc.add(pieceData);
        pieceData.clear();
    }
    if (ID_DOOR != NULL)
    {
        StaticJsonDocument<200> pieceData;
        pieceData["dName"] = "Smart Door";
        pieceData["dId"] = ID_DOOR;
        doc.add(pieceData);
        pieceData.clear();
    }
    if (ID_WINDOW != NULL)
    {
        StaticJsonDocument<200> pieceData;
        pieceData["dName"] = "Smart Window";
        pieceData["dId"] = ID_WINDOW;
        doc.add(pieceData);
        pieceData.clear();
    }
    if (ID_LIGHT != NULL)
    {
        StaticJsonDocument<200> pieceData;
        pieceData["dName"] = "Smart Light";
        pieceData["dId"] = ID_LIGHT;
        doc.add(pieceData);
        pieceData.clear();
    }
    if (ID_COVER != NULL)
    {
        StaticJsonDocument<200> pieceData;
        pieceData["dName"] = "Smart Cover";
        pieceData["dId"] = ID_COVER;
        doc.add(pieceData);
        pieceData.clear();
    }
    if (ID_SECURE != NULL)
    {
        StaticJsonDocument<200> pieceData;
        pieceData["dName"] = "Security Guard";
        pieceData["dId"] = ID_SECURE;
        doc.add(pieceData);
        pieceData.clear();
    }
    if (ID_FIREALARM != NULL)
    {
        StaticJsonDocument<200> pieceData;
        pieceData["dName"] = "Fire Alarm";
        pieceData["dId"] = ID_FIREALARM;
        doc.add(pieceData);
        pieceData.clear();
    }
    char dataString[measureJson(doc) + 1];
    serializeJson(doc, dataString, measureJson(doc) + 1);
    server->send(200, "application/json", dataString);
}
void startPortal()
{
    WiFi.mode(WIFI_AP);
    if (WiFi.getAutoConnect() == 0)
        WiFi.setAutoConnect(1);

    dnsServer.reset(new DNSServer());
    server.reset(new WebServer(80));
    dnsServer->setErrorReplyCode(DNSReplyCode::NoError);
    // DNSServer started with "*" domain name, all DNS requests will be passsed to WiFi.softAPIP()
    if (!dnsServer->start(53, "*", WiFi.softAPIP()))
    {
        // No socket available
    }
    startConnect = false;
    WiFi.softAP(AP_SSID, AP_PASSWD);
    delay(500);
    server->on("/", handleRoot);
    server->on("/wifiscan", handleScan);
    server->on("/wifisave", handleWifiSave);
    server->on("/getdevices", handleDevices);
    server->onNotFound(handleNotFound);
    server->begin(); // Web server start
    while (!startConnect)
    {
        // DNS
        dnsServer->processNextRequest();
        // HTTP
        server->handleClient();
        delay(1);
    }
    WiFi.mode(WIFI_STA);
    dispLcd.clear();
    dispLcd.print("Connecting to");
    dispLcd.setCursor(0, 1);
    dispLcd.print(">" + _ssid);
    WiFi.begin(_ssid.c_str(), _pass.c_str());
    // int connRes = WiFi.waitForConnectResult(20000);
    // return connRes == WL_CONNECTED;
}
void wifiSetup()
{
    WiFi.mode(WIFI_STA);
    if (WiFi_SSID() == "")
    {
        dispLcd.clear();
        dispLcd.print("Scan the QR code");
        dispLcd.setCursor(0, 1);
        dispLcd.print("to config WiFi.");
        dispLcd.blink();
        startPortal();
    }
    else
    {
        dispLcd.clear();
        dispLcd.print("Connecting to");
        dispLcd.setCursor(0, 1);
        dispLcd.print(">" + WiFi_SSID());
        WiFi.begin();
    }
    // bool connected = (WL_CONNECTED == WiFi.waitForConnectResult(20000));
    long startTime = millis();
    while (WiFi.status() != WL_CONNECTED)
    {
        if (millis() - startTime > 20000)
        {
            break;
        }
    }
    while (!(WiFi.status() == WL_CONNECTED && client.connect(SERVER_IP, SERVER_PORT_DATA)))
    {

        dispLcd.clear();
        dispLcd.print("Connect failed.");
        dispLcd.setCursor(0, 1);
        dispLcd.print("Try another WiFi!");
        delay(2000);
        dispLcd.clear();
        dispLcd.print("Scan the QR code");
        dispLcd.setCursor(0, 1);
        dispLcd.print("to config WiFi.");
        dispLcd.blink();
        startPortal();
        startTime = millis();
        while (WiFi.status() != WL_CONNECTED)
        {
            if (millis() - startTime > 20000)
            {
                break;
            }
        }
    }
    Serial.println("Connected.");
}

void timeUpdater()
{

    if (millis() - timeUpdateTime > 1000)
    {
        if (clockPoint)
        {
            tm1637.point(POINT_ON);
            clockPoint = false;
        }
        else
        {
            tm1637.point(POINT_OFF);
            clockPoint = true;
        }
        timeUpdateTime = millis();
        String timeAll = timeClient.getFormattedTime();
        int hrs = ((int(timeAll[0]) - 48) * 10 + int(timeAll[1]) - 48 + 8) % 24;
        int8_t timeDisp[4] = {(int8_t)(hrs / 10), (int8_t)(hrs % 10), (int8_t)(int(timeAll[3]) - 48), (int8_t)(int(timeAll[4]) - 48)};
        tm1637.display(timeDisp);
    }
}

void goInternet(void *pvParameters)
{
    while (1)
    {
        if (client.available()) //如果缓冲区字符串大于0
        {
            processResp(client.readStringUntil('\0'));
        }
        timeUpdater();

        if (upCycle < UPLOADCYCLE)
        {
            upCycle++;
        }
        else
        {
            int tCount = 0;
            while (!client.connected() && tCount < 3)
            {
                if (!client.connect(SERVER_IP, SERVER_PORT_DATA))
                {
                    dispLcd.clear();
                    dispLcd.print("Connecting...");
                    if (tCount == 1)
                    {
                        dispLcd.setCursor(0, 1);
                        dispLcd.print("Try another WiFi");
                    }
                }
                tCount++;
            }
            if (!client.connected())
            {
                delay(1000);
                ESP.restart();
            }

            if (sendOrDownload)
            {
                sendData();
                sendOrDownload = false;
            }
            else
            {
                getOpt();
                sendOrDownload = true;
            }
            upCycle = 0;
        }
        vTaskDelay(20);
    }
}
void setup()
{

    pinMode(PIN_HUMANALARM, OUTPUT);
    pinMode(PIN_FIREALARM, OUTPUT);
    digitalWrite(PIN_FIREALARM, LOW);
    digitalWrite(PIN_HUMANALARM, LOW);

    Serial.begin(115200); //调试串口开启
    dispLcd.begin(16, 2); // initialize the lcd
    dispLcd.createChar(0, tempUnit);
    dispLcd.setBacklight(255);
    dispLcd.home();
    dispLcd.clear();

    dispLcd.print("Initializing...");
    dispLcd.setCursor(0, 1);
    dispLcd.print("> Setup WiFi.");
    dispLcd.blink();
    delay(1000);
    pinMode(0, INPUT);
    if (!digitalRead(0))
    {
        dispLcd.clear();
        dispLcd.print("Initializing...");
        Serial.println("offline mode");
        dispLcd.setCursor(0, 1);
        dispLcd.print("> Offline Mode");
        offlineMode = true;
        delay(3000);
    }
    else
    {
        wifiSetup();
        getOpt();
        tm1637.set();
        tm1637.init();
        timeClient.begin();
        timeClient.update(); //时间同步
        timeUpdater();
        xTaskCreatePinnedToCore(goInternet, "Task2", 10240, NULL, 3, NULL, 0);
    }
    //到这已经连上网了
    dispLcd.home();
    dispLcd.noAutoscroll();
    dispLcd.noBlink();
    dispLcd.clear();
    dispLcd.print("  Welcome Home! ");
    dispLcd.setCursor(0, 1);
    dispLcd.print("       ^w^");
    delay(1000);

    //舵机需要
    ESP32PWM::allocateTimer(0);
    ESP32PWM::allocateTimer(1);
    ESP32PWM::allocateTimer(2);
    ESP32PWM::allocateTimer(3);
    coverServo.setPeriodHertz(50); // standard 50 hz servo
    doorServo.setPeriodHertz(50);  // standard 50 hz servo
    coverServo.attach(PIN_COVERGEAR, 1000, 2000);
    doorServo.attach(PIN_DOORGEAR, 1000, 2000);

    IrReceiver.enableIRIn(); // 红外初始化
    pixels.begin();          //灯初始化
    dht.begin();             // DHT初始化

    // Serial.println(timeClient.getFormattedTime());

    pinMode(2, OUTPUT); //自带的LED灯初始化
    pinMode(PIN_FAN, OUTPUT);
    pinMode(PIN_BUZZER, OUTPUT);
    pinMode(PIN_WINDOWBUTTON, INPUT);
    pinMode(PIN_HUMANDETECT, INPUT);
    pinMode(PIN_FIREDETECT, INPUT);

    debouncer.attach(PIN_WINDOWBUTTON); //将抖动对象绑定到按钮针脚
    debouncer.interval(5);              //间隔是5ms
    alreadySetup = true;
}

void loop()
{
    digitalWrite(2, LOW);
    winStateUpdater();
    alarmUpdater();

    if (IrReceiver.decode(&IrResults)) //解码成功，收到一组红外讯号
    {
        unsigned long command = IrResults.value;
        // Serial.println(IrReceiver.decodedIRData.decodedRawData, HEX);
        if (command == 0xffa25d || command == 0xff629d || command == 0xffe21d || command == 0xff22dd || command == 0xff02fd || command == 0xffc23d || command == 0xffe01f || command == 0xffa857 || command == 0xff906f || command == 0xff9867)
            pairPassword(command);    // 按键0-9判断密码
        else if (command == 0xff18e7) //按键上灯加亮度
            lightIncrease();
        else if (command == 0xff4ab5) //按键下灯降低亮度
            lightDecrease();
        else if (command == 0xff10ef)
            // {
            setSecMode(2);
        // } //按键左遮阳板收
        // else if (command == 0xff5aa5)
        // {
        //     coverOpen();
        // }                             //按键右遮阳板开
        else if (command == 0xff38c7) //按键OK电风扇档位
            fanControl();
        else if (command == 0xff6897) //按键*换灯颜色
            lightChangeColor();
        else if (command == 0xffb04f)
        {
            coverControl();
        } //按键#控制光敏窗户
        // lightWindowControl();
        IrReceiver.resume();
    }

    getTempHumi();
    getIllu();

    if (millis() - dispUpdateTime > refreshRate)
    {
        dispUpdateTime = millis();
        const char *line1 = dispLine1.c_str();
        const char *line2 = dispLine2.c_str();
        dispLcd.clear();
        dispLcd.print(line1);
        if (line1[0] == 'T' && line2[0] == 'H')
        {
            dispLcd.write(byte(0));
        }
        dispLcd.setCursor(0, 1);
        dispLcd.print(dispLine2);
    }
    digitalWrite(2, HIGH);

    delay(50);
}
